/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright (c) 2021 DilOS
 */

/*
 * platform specific implementations
 */

#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/efi_partition.h>
#include <sys/param.h>
#include <sys/avl.h>
#include <libzutil.h>

#include "zutil_import.h"

#define BACKUP_SLICE	"s2"

/*
 * Append partition suffix to an otherwise fully qualified device path.
 */
int
zfs_append_partition(char *path, size_t max_len)
{
	char *s;
	int len = strlen(path);

	if (((s = strrchr(path, 's')) == NULL &&
	    (s = strchr(path, 'p')) == NULL) || !isdigit(*(s + 1))) {
		if (len + 2 >= max_len)
			return (-1);
		(void) strlcat(path, "s0", max_len);
		len += 2;
	}

	return (len);
}

/*
 * Remove partition suffix from a vdev path. If it starts with c#, and ends
 * with "s0" or "s1", chop the slice off, or if it ends with "s0/old" or
 * "s1/old", remove the slice from the middle.
 *
 * caller must free the returned string
 */
char *
zfs_strip_partition(const char *path)
{
	char *tmp;

	tmp = strdup(path);
	if (tmp == NULL)
		return (NULL);

	if (CTD_CHECK(tmp)) {
		int pathlen = strlen(path);
		if (strcmp(&tmp[pathlen - 2], "s0") == 0 ||
		    strcmp(&tmp[pathlen - 2], "s1") == 0) {
			tmp[pathlen - 2] = '\0';
		} else if (pathlen > 6 &&
		    (strcmp(&tmp[pathlen - 6], "s0/old") == 0 ||
		    strcmp(&tmp[pathlen - 6], "s1/old") == 0)) {
			(void) strcpy(&tmp[pathlen - 6], "/old");
		}
	}

	return (tmp);
}

/*
 * Same as zfs_strip_partition, but allows "/dev/" to be in the pathname
 *
 * path:	/dev/sda1
 * returns:	/dev/sda
 *
 * Returned string must be freed.
 */
static char *
zfs_strip_partition_path(const char *path)
{
	char *newpath;
	char *sd_offset;
	char *new_sd;

	newpath = strdup(path);
	if (newpath == NULL)
		return (NULL);

	/* Point to "sda1" part of "/dev/sda1" */
	sd_offset = strrchr(newpath, '/') + 1;

	/* Get our new name "sda" */
	new_sd = zfs_strip_partition(sd_offset);
	if (new_sd == NULL) {
		free(newpath);
		return (NULL);
	}

	/* Paste the "sda" where "sda1" was */
	strlcpy(sd_offset, new_sd, strlen(sd_offset) + 1);

	/* Free temporary "sda" */
	free(new_sd);

	return (newpath);
}

/*
 * Strip the DISK_ROOT of a device path.
 */
const char *
zfs_strip_path(const char *path)
{
	static const char	disk_rootd[] = DISK_ROOT "/";
	static const size_t	drlen = sizeof(disk_rootd) - 1;

	if (strncmp(path, disk_rootd, drlen) == 0)
		path += drlen;

	return (path);
}

/*
 * By "whole disk" we mean an entire physical disk (something we can
 * label, toggle the write cache on, etc.) as opposed to the full
 * capacity of a pseudo-device such as lofi or did.  We act as if we
 * are labeling the disk, which should be a pretty good test of whether
 * it's a viable device or not.  Returns B_TRUE if it is and B_FALSE if
 * it isn't.
 *
 * The implementation is taken from zpool/zpool_vdev.c is_whole_disk(...)
 */
boolean_t
zfs_dev_is_whole_disk(const char *dev_name)
{
	struct dk_gpt *label;
	int fd;
	char path[MAXPATHLEN];

	(void) snprintf(path, sizeof (path), "%s%s%s",
	    ZFS_RDISK_ROOT, strrchr(dev_name, '/'), BACKUP_SLICE);
	if ((fd = open(path, O_RDWR | O_NDELAY | O_CLOEXEC)) < 0)
		return (B_FALSE);

	if (efi_alloc_and_init(fd, EFI_NUMPAR, &label) != 0) {
		(void) close(fd);
		return (B_FALSE);
	}
	efi_free(label);
	(void) close(fd);

	return (B_TRUE);
}

/*
 * Lookup the underlying device for a device name
 *
 * Often you'll have a symlink to a device, a partition device,
 * or a multipath device, and want to look up the underlying device.
 * This function returns the underlying device name.  If the device
 * name is already the underlying device, then just return the same
 * name.  If the device is a DM device with multiple underlying devices
 * then return the first one.
 *
 * For example:
 *
 * 1. /dev/disk/by-id/ata-QEMU_HARDDISK_QM00001 -> ../../sda
 * dev_name:	/dev/disk/by-id/ata-QEMU_HARDDISK_QM00001
 * returns:	/dev/sda
 *
 * 2. /dev/mapper/mpatha (made up of /dev/sda and /dev/sdb)
 * dev_name:	/dev/mapper/mpatha
 * returns:	/dev/sda (first device)
 *
 * 3. /dev/sda (already the underlying device)
 * dev_name:	/dev/sda
 * returns:	/dev/sda
 *
 * 4. /dev/dm-3 (mapped to /dev/sda)
 * dev_name:	/dev/dm-3
 * returns:	/dev/sda
 *
 * 5. /dev/disk/by-id/scsi-0QEMU_drive-scsi0-0-0-0-part9 -> ../../sdb9
 * dev_name:	/dev/disk/by-id/scsi-0QEMU_drive-scsi0-0-0-0-part9
 * returns:	/dev/sdb
 *
 * 6. /dev/disk/by-uuid/5df030cf-3cd9-46e4-8e99-3ccb462a4e9a -> ../dev/sda2
 * dev_name:	/dev/disk/by-uuid/5df030cf-3cd9-46e4-8e99-3ccb462a4e9a
 * returns:	/dev/sda
 *
 * Returns underlying device name, or NULL on error or no match.
 *
 * NOTE: The returned name string must be *freed*.
 */
char *
zfs_get_underlying_path(const char *dev_name)
{
	if (dev_name == NULL)
		return (NULL);

	/* dev_name not a DM device, so just un-symlinkize it */
	char *tmp = realpath(dev_name, NULL);
	if (tmp == NULL)
		return (NULL);

	char *name = zfs_strip_partition_path(tmp);
	free(tmp);

	return (name);
}

char *
zfs_get_enclosure_sysfs_path(const char *dev_name)
{
	(void) dev_name;
	return (NULL);
}

boolean_t
is_mpath_whole_disk(const char *path)
{
	(void) path;
	return (B_FALSE);
}

/*
 * This provides a very minimal check whether a given string is likely a
 * c#t#d# style string.  Users of this are expected to do their own
 * verification of the s# part.
 */
/*#define CTD_CHECK(str)  (str && str[0] == 'c' && isdigit(str[1]))*/

/*
 * More elaborate version for ones which may start with "/dev/dsk/"
 * and the like.
 */
int
ctd_check_path(const char *str)
{
	/*
	 * If it starts with a slash, check the last component.
	 */
	if (str && str[0] == '/') {
		const char *tmp = strrchr(str, '/');

		/*
		 * If it ends in "/old", check the second-to-last
		 * component of the string instead.
		 */
		if (tmp != str && strcmp(tmp, "/old") == 0) {
			for (tmp--; *tmp != '/'; tmp--)
				;
		}
		str = tmp + 1;
	}
	return (CTD_CHECK(str));
}
